package dao

import (
	"database/sql"
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"go.uber.org/zap"
	"net/url"
	"reflect"
	"time"
)

type MyDB struct {
	*sql.DB
}

var DB MyDB

func init() {
	dataSourceName := fmt.Sprintf("root:root@tcp(192.168.56.22:3306)/blog_test?charset=utf8&loc=%s&parseTime=true", url.QueryEscape("Asia/Shanghai"))
	db, err := sql.Open("mysql", dataSourceName)
	if err != nil {
		zap.S().Errorf("连接数据库失败;原因:%s", err.Error())
		panic(err)
	}
	db.SetMaxOpenConns(100)
	db.SetMaxIdleConns(5)
	db.SetConnMaxLifetime(3 * time.Minute)
	db.SetConnMaxIdleTime(time.Minute)
	err = db.Ping()
	if err != nil {
		zap.S().Errorf("连接数据库失败;原因:%s", err.Error())
		_ = db.Close()
		panic(err)
	}
	DB = MyDB{db}
}

func (db *MyDB) SelectList(sql string, dest interface{}, args ...interface{}) error {
	rows, err := db.Query(sql, args...)
	if err != nil {
		zap.S().Errorf("执行sql失败,sql:%s;args:%v,err:%s", sql, args, err.Error())
		return err
	}
	// 获取数据库列信息
	columns, err := rows.Columns()
	if err != nil {
		zap.S().Errorf("获取数据库列信息失败,sql:%s;args:%v,err:%s", sql, args, err.Error())
		return err
	}
	// 获取结果类型
	destType := reflect.TypeOf(dest)
	if destType.Kind() != reflect.Pointer || destType.Elem().Kind() != reflect.Slice {
		return errors.New("dest not pointer or slice")
	}
	// 获取元素类型
	elemType := destType.Elem().Elem()
	if elemType.Kind() != reflect.Struct {
		return errors.New("dest elem not struct")
	}
	// 储存结果的切片
	resultSlice := reflect.MakeSlice(destType.Elem(), 0, 0)
	// 储存表字段和struct元素的映射关系
	colIdxMap := make(map[string]string)
	for i := 0; i < elemType.NumField(); i++ {
		field := elemType.Field(i)
		colName := field.Tag.Get("orm")
		colIdxMap[colName] = field.Name
	}
	// 遍历查询结果
	for rows.Next() {
		// 创建一个新的结构体
		newObj := reflect.New(elemType).Elem()
		// 创建一个切片储存字段值的指针
		fieldPointers := make([]interface{}, len(columns))
		for i, column := range columns {
			// 查询字段名
			fieldName := colIdxMap[column]
			// 获取字段类型
			fieldType := newObj.FieldByName(fieldName).Type()
			// 创建该字段的指针
			fieldPtr := reflect.New(fieldType).Interface()
			// 将指针加入切片
			fieldPointers[i] = fieldPtr
		}
		// 将切片传递给Scan
		err := rows.Scan(fieldPointers...)
		if err != nil {
			return err
		}
		for i, column := range columns {
			fieldName := colIdxMap[column]
			// 反射设置结构体字段
			newObj.FieldByName(fieldName).Set(reflect.ValueOf(fieldPointers[i]).Elem())
		}
		// 将结构体加入到slice
		resultSlice = reflect.Append(resultSlice, newObj)
	}
	reflect.ValueOf(dest).Elem().Set(resultSlice)
	return nil
}

func (db *MyDB) SelectOne(dest interface{}, sql string, args ...interface{}) error {
	row, err := db.Query(sql, args...)
	if err != nil {
		zap.S().Errorf("执行sql失败,sql:%s;args:%v,err:%s", sql, args, err.Error())
		return err
	}
	destType := reflect.TypeOf(dest)
	if destType.Kind() != reflect.Pointer || destType.Elem().Kind() != reflect.Struct {
		return errors.New("dest not pointer or struct")
	}
	columns, err := row.Columns()
	if err != nil {
		zap.S().Errorf("获取数据库列信息失败,sql:%s;args:%v,err:%s", sql, args, err.Error())
		return err
	}
	// 储存表字段和struct元素的映射关系
	colIdxMap := make(map[string]string)
	elem := destType.Elem()
	for i := 0; i < elem.NumField(); i++ {
		field := elem.Field(i)
		colName := field.Tag.Get("orm")
		colIdxMap[colName] = field.Name
	}
	// 创建一个储存结果的指针数组
	fieldPointers := make([]interface{}, len(columns))
	newObj := reflect.New(elem).Elem()
	for i, column := range columns {
		// 查询字段名
		fieldName := colIdxMap[column]
		// 获取字段类型
		fieldType := newObj.FieldByName(fieldName).Type()
		// 创建该字段的指针
		fieldPtr := reflect.New(fieldType).Interface()
		// 将指针加入切片
		fieldPointers[i] = fieldPtr
	}
	if row.Next() {
		err = row.Scan(fieldPointers...)
		if err != nil {
			return err
		}
	}
	for i, column := range columns {
		fieldName := colIdxMap[column]
		// 反射设置结构体字段
		newObj.FieldByName(fieldName).Set(reflect.ValueOf(fieldPointers[i]).Elem())
	}
	reflect.ValueOf(dest).Elem().Set(newObj)
	return nil
}
